﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using SmokinAces.Object_Classes;
using SmokinAces.Object_Classes.Objects;

namespace SmokinAces
{
	/// <summary>
	/// Game effect: should take care of every thing that basic effect does,
	/// and anything else that should be required in this game.
	/// </summary>
	public class Game_Effect : Logic_Object
	{
		// everybody uses the same effect.
		// (might change, but shouldn't change the API)
        Main_Engine parent;
		static Effect effect = null;
		public Game_Effect(Main_Engine Dad)
		{
            parent = Dad;
			if(effect == null)
			{
				effect = parent.Content.Load<Effect>("effects");
				effect.CurrentTechnique = effect.Techniques["GeneralTechnique"];
			}
			properties.Add("General", new GeneralProperties());
			properties.Add("HeatWave", new HeatWaveProperties());
			properties.Add("RainStorm", new RainStormProperties((Main_Engine)parent, effect));
            properties.Add("ThunderStorm", new RainStormProperties((Main_Engine)parent, effect));
		}

		private bool tinting = false;
		private bool masking = false;
		private bool blending = false;

		// these are the same during each render pass, so only the
		// game engine should set them.
		public static Matrix World
		{
			set { effect.Parameters["World"].SetValue(value); }
			get { return effect.Parameters["World"].GetValueMatrix(); }
		}
		public static Matrix View
		{
			set { effect.Parameters["View"].SetValue(value); }
		}
		public static Matrix Projection
		{
			set { effect.Parameters["Projection"].SetValue(value); }
		}

		// these can be used on a case by case basis
		public void EnableTinting() { tinting = true; }
		public void EnableMasking() { masking = true; }
		public void EnableAlphaBlending() { blending = true; }
		public void DisableTinting() { tinting = false; }
		public void DisableMasking() { masking = false; }
		public void DisableAlphaBlending() { blending = false; }

		// these functions switch between techniques
		public void SetGeneric() { SetTechnique("General"); }
		public void SetHeatWave() { SetTechnique("HeatWave"); }
		public void SetRainStorm(float dist)
		{
			RainStorm.RainDist = dist;
			ThunderStorm.ThunderDist = 0f;
			SetTechnique("RainStorm");
		}
		public void SetThunderStorm(float dist, float freq)
		{
			ThunderStorm.RainDist = dist;
			ThunderStorm.ThunderDist = freq;
			SetTechnique("ThunderStorm");
		}
		private void SetTechnique(string tech)
		{
			if(currentProperties != null) stopQueue.Enqueue(currentProperties);
			if(properties.ContainsKey(tech))
			{
				currentProperties = properties[tech];
				startQueue.Enqueue(currentProperties);
				effect.CurrentTechnique = effect.Techniques[currentProperties.Technique];
			}
			else currentProperties = null;
		}

		// functions that impact time ans season
		public void SetNormalLight()
		{
			effect.Parameters["diffuse"].SetValue(new Vector4(1, 1, 1, 1));
			effect.Parameters["specular"].SetValue(new Vector4(0, 0, 0, 0));
		}
		public void SetBrightLight()
		{
			effect.Parameters["diffuse"].SetValue(new Vector4(0.7f, 0.7f, 0.7f, 1));
			effect.Parameters["specular"].SetValue(new Vector4(1.6f, 1.2f, 1f, 1));
		}
		public void SetMoonLight()
		{
			effect.Parameters["diffuse"].SetValue(new Vector4(0.25f, 0.25f, 0.45f, 1)); ;
			effect.Parameters["specular"].SetValue(new Vector4(1f, 1f, 1f, 1));
		}

		private Color alphaColor = Color.White;
		private Texture2D baseTexture = null;
		private Texture2D maskTexture = null;

		public Color AlphaColor
		{
			set { alphaColor = value; }
		}
		public Texture2D BaseTexture
		{
			set { baseTexture = value; }
		}
		public Texture2D MaskTexture
		{
			set { maskTexture = value; }
		}

		public void Begin()
		{
			effect.Parameters["SrcTexture"].SetValue(baseTexture);
			effect.Parameters["MskTexture"].SetValue(maskTexture);
			effect.Parameters["AlphaColor"].SetValue(alphaColor.ToVector4());
			effect.Parameters["BlendAlpha"].SetValue(blending);
			effect.Parameters["MaskTex"].SetValue(masking);
			effect.Parameters["Tinting"].SetValue(tinting);

			effect.Begin();
			//effect.CurrentTechnique.Passes[0].Begin();
		}
		public void End()
		{
			//effect.CurrentTechnique.Passes[0].End();
			effect.End();
		}
		public EffectTechnique CurrentTechnique
		{
			get
			{
				return effect.CurrentTechnique;
			}
		}

		#region Logic_Object Members

		Queue<EffectPropertySet> startQueue = new Queue<EffectPropertySet>();
		Queue<EffectPropertySet> stopQueue = new Queue<EffectPropertySet>();

		public void Update(GameTime gameTime)
		{
			while(stopQueue.Count > 0) stopQueue.Dequeue().Stop((Game_Engine)parent, gameTime, effect);
			while(startQueue.Count > 0) startQueue.Dequeue().Start((Game_Engine)parent, gameTime, effect);

			// update effect properties
			effect.Parameters["time"].SetValue((float)gameTime.TotalRealTime.TotalSeconds);
			if((currentProperties != null) && (!currentProperties.UpToDate))
				currentProperties.ApplyProperties(gameTime, effect);
		}
        public bool startUpdate()
        {
            parent.add_Update_Object(Update);
			if(currentProperties != null)
				foreach(Logic_Object obj in currentProperties.GetLogicObjects())
				{
					obj.startUpdate();
				}
            return true;
        }
        public bool stopUpdate()
        {
			parent.add_Update_Object(Update);
			if(currentProperties != null)
				foreach(Logic_Object obj in currentProperties.GetLogicObjects())
				{
					obj.stopUpdate();
				}
            parent.remove_Update_Object(Update);
            return true;
        }
		#endregion

		#region Game_Object Members

		public bool dispose()
		{
			effect.Dispose();
			GC.SuppressFinalize(this);
			return true;
		}

		#endregion

		// properties for specific effects
		static Dictionary<string, EffectPropertySet> properties = new Dictionary<string, EffectPropertySet>();
		private EffectPropertySet currentProperties = null;
		public abstract class EffectPropertySet
		{
			public abstract string Technique { get; }
			public bool UpToDate { get; protected set; }
			public virtual void Start(Game_Engine game, GameTime time, Effect effect) { }
			public virtual void Stop(Game_Engine game, GameTime time, Effect effect) { }
			public abstract void ApplyProperties(GameTime time, Effect effect);
			public virtual IEnumerable<Logic_Object> GetLogicObjects() { return new Logic_Object[] { }; }
			public virtual IEnumerable<Draw_Object> GetDrawObject() { return new Draw_Object[] { }; }
		}
		public sealed class GeneralProperties : EffectPropertySet
		{
			public override string Technique
			{
				get { return "GeneralTechnique"; }
			}
			public override void ApplyProperties(GameTime time, Effect effect)
			{
				this.UpToDate = true;
			}
		}
		public sealed class HeatWaveProperties : EffectPropertySet
		{
			float _phase = 0;
			float _amp = 0;
			public float Phase
			{
				get { return _phase; }
				set { UpToDate = false; _phase = value; }
			}
			public float Amplitude
			{
				get { return _amp; }
				set { UpToDate = false; _amp = value; }
			}
			public override void ApplyProperties(GameTime time, Effect effect)
			{
				effect.Parameters["HeatPhase"].SetValue(_phase);
				effect.Parameters["HeatStrength"].SetValue(_amp);
				UpToDate = true;
			}
			public override string Technique
			{
				get { return "HeatWaveTechnique"; }
			}
		}
		public sealed class RainStormProperties : EffectPropertySet
		{
			Texture2D thunder;
			Rain_System rain;

			float rdist = 156;
			float tdist = 0.05f;
			public float RainDist
			{
				get { return rdist; }
				set { rdist = value; UpToDate = false; }
			}
			public float ThunderDist
			{
				get { return tdist; }
				set { tdist = value; UpToDate = false; }
			}
			private float Amplitude(float t)
			{
				return (float)(Amplitude((double)t, 1.3, -0.2, 0.2, 0.003) +
					Amplitude((double)t, 1.0, -0.2, 0.6, 0.003));
			}
			private double Amplitude(double t, double amp, double offset, double w, double p)
			{
				// max(0, min(1, amp.*p.*(((x.-w).^2 + p).\1.0) .+ offset));
				return Math.Max(0.0, Math.Min(1, offset + amp * p *
					(1.0/(Math.Pow(t - w, 2.0) + p))));
			}
			public RainStormProperties(Main_Engine game, Effect effect)	
			{
				thunder = new Texture2D(game.GraphicsDevice, 1024, 16, 1, TextureUsage.None, SurfaceFormat.Single);

				float[] data = new float[16 * 1024];
				for(int i = 0; i < 16; i++)
				{
					float scale = ((float)(i + 1))/16f;
					for(int j = 0; j < 1024; j++)
					{
						float t = ((float)j)/1024f;
						data[i * 1024 + j] = scale * Amplitude(t);
					}
				}
				thunder.SetData<float>(data);

				effect.Parameters["tdsTexture"].SetValue(thunder);
				effect.Parameters["tdsIsThunder"].SetValue(false);
				effect.Parameters["tdsTime"].SetValue(0f);

				rain = new Rain_System(game);
			}
			public override void Start(Game_Engine game, GameTime time, Effect effect)
			{
				base.Start(game, time, effect);
				rain.Start(time, rdist);
			}
			public override void Stop(Game_Engine game, GameTime time, Effect effect)
			{
				base.Stop(game, time, effect);
				rain.Stop();
			}
			public override void ApplyProperties(GameTime time, Effect effect)
			{
				effect.Parameters["tdsTime"].SetValue(
					effect.Parameters["tdsTime"].GetValueSingle() + (float)time.ElapsedRealTime.TotalSeconds);

				this.UpToDate = false;
			}
			public override string Technique
			{
				get { return "RainStormTechnique"; }
			}
			public override IEnumerable<Logic_Object> GetLogicObjects() { yield return rain; }
			public override IEnumerable<Draw_Object> GetDrawObject() { yield return rain; }
		}
		public HeatWaveProperties HeatWave { get { return (HeatWaveProperties)properties["HeatWave"]; } }
		public RainStormProperties RainStorm { get { return (RainStormProperties)properties["RainStorm"]; } }
		public RainStormProperties ThunderStorm { get { return (RainStormProperties)properties["ThunderStorm"]; } }
		private GeneralProperties General { get { return (GeneralProperties)properties["General"]; } }
	}
}
